%%% Copyright 2010 Google Inc.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%%     http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.

%%% @copyright 2010 Google Inc. All Rights Reserved.
%%% @author <jacoby@google.com> Jacoby Thwaites [http://sites/madcloud]
%%% @version {@version}
%%% @doc
%%% Tests for name-ref sets module {@link nr_set}.
%%% @see nr_set
%%%

-module(nr_set_tests).

-include_lib("eunit/include/eunit.hrl").

-export([populate_nr_set/1]).

nr_set_test_() ->
  {inorder,
    {setup,

     % Create the test set.
     fun() ->
       nr_set:new()
     end,

     % No teardown
     fun(_NrSet) ->
          ok
     end,

     % Test generator takes constructed nr_set.
     fun(NrSet) ->
          [basic_tests(NrSet),
           dict_set_tests(NrSet),
           proplist_tests(NrSet),
           fold_tests(NrSet),
           map_tests(NrSet)]
     end}}.

%% Basic tests using nr_set created at setup time.
basic_tests(NrSet) ->
  [
    % Test store, count and get by name and ref.
    fun() ->
         NewNrSet = nr_set:store({"one", 1}, NrSet),
         ?assertEqual(1, nr_set:count(NewNrSet)),
         ?assertEqual({"one", 1}, nr_set:get_by_ref(1, NewNrSet)),
         ?assertEqual({"one", 1}, nr_set:get_by_name("one", NewNrSet))
    end,

    % Test store by list.
    fun() ->
         NewNrSet = nr_set:store([{"one", 1}, {"two", 2}], NrSet),
         ?assertEqual({"one", 1}, nr_set:get_by_ref(1, NewNrSet)),
         ?assertEqual({"one", 1}, nr_set:get_by_name("one", NewNrSet)),
         ?assertEqual({"two", 2}, nr_set:get_by_ref(2, NewNrSet)),
         ?assertEqual({"two", 2}, nr_set:get_by_name("two", NewNrSet))
    end,

    % Test name_for_ref and ref_for_name
    fun() ->
         NewNrSet = populate_nr_set(NrSet),
         ?assertEqual({field, {3, 0, 0}},
                      nr_set:ref_for_name("three", NewNrSet)),
         ?assertEqual("four",
                      nr_set:name_for_ref({op, {4, 0, 0}}, NewNrSet))
    end,

    % Test '$not_found' return on retrieval functions.
    fun() ->
         NewNrSet = populate_nr_set(NrSet),
         ?assertEqual('$not_found',
                      nr_set:get_by_ref({op, {50, 0, 0}}, NewNrSet)),
         ?assertEqual('$not_found',
                      nr_set:get_by_name("fifty", NewNrSet)),
         ?assertEqual('$not_found',
                      nr_set:name_for_ref({op, {51, 0, 0}}, NewNrSet)),
         ?assertEqual('$not_found',
                      nr_set:ref_for_name("sixty", NewNrSet))
    end,

    % Test deletion functions.
    fun() ->
         NewNrSet = populate_nr_set(NrSet),
         D1 = nr_set:delete_by_name("three", NewNrSet),
         D2 = nr_set:delete_by_ref({folder, {1, 0, 0}}, D1),
         ?assertEqual('$not_found',
                      nr_set:get_by_name("one", D2)),
         ?assertEqual('$not_found',
                      nr_set:get_by_ref({field, {3, 0, 0}}, D2)),
         ?assertEqual(2, nr_set:count(D2))
    end,

    % Test dup prevention on store functions.
    fun() ->
         NewNrSet = populate_nr_set(NrSet),
         S1 = nr_set:store({"two", {op, {2, 0, 1}}}, NewNrSet),
         ?assertEqual(4, nr_set:count(S1)),
         ?assertEqual("two", nr_set:name_for_ref({op, {2, 0, 1}}, S1))
    end,

    % Test dup prevention on store list functions.
    fun() ->
         NewNrSet = populate_nr_set(NrSet),
         S1 = nr_set:store([{"two", {op, {2, 0, 2}}},
                            {"five", {field, {5, 0, 0}}},
                            {"six", {folder, {6, 0, 0}}},
                            {"five", {field, {5, 0 ,1}}}], NewNrSet),
         ?assertEqual(6, nr_set:count(S1)),
         ?assertEqual("five", nr_set:name_for_ref({field, {5, 0 ,1}}, S1)),
         ?assertEqual({op, {2, 0, 2}}, nr_set:ref_for_name("two", S1))
     end,

    % Test is_nr_set function.
    fun() ->
         ?assert(nr_set:is_nr_set(NrSet)),
         ?assertNot(nr_set:is_nr_set({foo, bar}))
    end
   ].

%% Dict and set tests using nr_set created at setup time.
dict_set_tests(NrSet) ->
  [
    % Test dict creation functions.
    fun() ->
         D1 = nr_set:dict_by_name(populate_nr_set(NrSet)),
         ?assertEqual(4, dict:size(D1)),
         ?assertMatch({field, {3, 0, 0}}, dict:fetch("three", D1)),

         D2 = nr_set:dict_by_ref(populate_nr_set(NrSet)),
         ?assertEqual(4, dict:size(D2)),
         ?assertMatch("two", dict:fetch({op, {2, 0, 0}}, D2))
    end,

    % Test set creation functions.
    fun() ->
         S1 = nr_set:name_set(populate_nr_set(NrSet)),
         ?assertEqual(4, sets:size(S1)),
         ?assert(sets:is_element("four", S1)),

         S2 = nr_set:ref_set(populate_nr_set(NrSet)),
         ?assertEqual(4, sets:size(S2)),
         ?assert(sets:is_element({folder, {1, 0, 0}}, S2))
    end,

    % Test creation from name dict.
    fun() ->
         Nr = nr_set:from_dict_key_name(
                dict:from_list(
                  [{"one", {folder, {1, 0, 0}}},
                   {"two", {op, {2, 0, 0}}},
                   {"three", {field, {3, 0, 0}}},
                   {"four", {op, {4, 0, 0}}}])),
         ?assertEqual(4, nr_set:count(Nr)),
         ?assertEqual("four", nr_set:name_for_ref({op, {4, 0, 0}}, Nr))
    end,

    % Test creation from ref dict.
    fun() ->
         Nr = nr_set:from_dict_key_ref(
                dict:from_list(
                  [{{folder, {1, 0, 0}}, "one"},
                   {{op, {2, 0, 0}}, "two"},
                   {{field, {3, 0, 0}}, "three"},
                   {{op, {4, 0, 0}}, "four"}])),
         ?assertEqual(4, nr_set:count(Nr)),
         ?assertEqual({"two", {op, {2, 0, 0}}},
                      nr_set:get_by_name("two", Nr))
     end
    ].

%% Proplist tests, switching from lists keyed by name to ref and vice versa.
proplist_tests(NrSet) ->
  [
    % Test creation from proplist
    fun() ->
         Nr = nr_set:from_proplist([{"one", {folder, {1, 0, 0}}},
                   {"two", {op, {2, 0, 0}}},
                   {"three", {field, {3, 0, 0}}},
                   {"four", {op, {4, 0, 0}}}]),
         ?assertEqual(4, nr_set:count(Nr)),
         ?assertEqual({field, {3, 0, 0}},
                      nr_set:ref_for_name("three", Nr))
    end,

    % Test switch from name to ref.
    fun() ->
         Nr = populate_nr_set(NrSet),
         From = [{"one", foo}, {"four", bar}],
         To = nr_set:to_proplist_by_ref(From, Nr),
         ?assertEqual(2, length(To)),
         ?assertEqual(foo, proplists:get_value({folder, {1, 0, 0}}, To)),
         ?assertEqual(bar, proplists:get_value({op, {4, 0, 0}}, To))
     end,

    % Test switch from ref to name.
    fun() ->
        Nr = populate_nr_set(NrSet),
        From = [{{folder, {1, 0, 0}}, foo}, {{op, {4, 0, 0}}, bar}],
        To = nr_set:to_proplist_by_name(From, Nr),
        ?assertEqual(2, length(To)),
        ?assertEqual(foo, proplists:get_value("one", To)),
        ?assertEqual(bar, proplists:get_value("four", To))
    end
  ].

fold_tests(NrSet) ->
  [
    % Test folding function over name/ref pairs.
    fun() ->
         Nr = populate_nr_set(NrSet),
         Count = nr_set:fold(
                   fun(Name, Ref, Acc) ->
                        ?assertEqual(Name, nr_set:name_for_ref(Ref, Nr)),
                        Acc + 1
                   end, 0, Nr),
         ?assertEqual(4, Count)
    end
  ].

map_tests(NrSet) ->
  [
    % Test map function over name/ref pairs.
    fun() ->
         Nr = populate_nr_set(NrSet),
         NewNrSet = nr_set:map(
                      fun(Name, Ref) ->
                           ?assertEqual(Name, nr_set:name_for_ref(Ref, Nr)),
                           {Type, {A, B, 0}} = Ref,
                           {Type, {A, B, 1}}
                      end, Nr),
         ?assertEqual({field, {3, 0, 1}},
                      nr_set:ref_for_name("three", NewNrSet))
    end
  ].

%% Utility functions.
populate_nr_set(NrSet) ->
  nr_set:store([{"one", {folder, {1, 0, 0}}},
                {"two", {op, {2, 0, 0}}},
                {"three", {field, {3, 0, 0}}},
                {"four", {op, {4, 0, 0}}}], NrSet).
